local skynet = require "skynet"

local CMD = {}
local SOCKET = {}
local user_agent = {} -- { uid -> agent } 
local recycle_queue = {} 
local recycled_agents = 0

--------------------------------

local loginservice 
local gateservice

local agent_pool = {} 
local agent_pool_min_size = skynet.getenv("watchdog_agent_pool_min_size") or 10		 -- 代理池大小
local agent_bgsave_interval = skynet.getenv("watchdog_bgsave_interval") or 600		 -- 代理备份快照的时间间隔
local agent_check_idle_interval = skynet.getenv("watchdog_check_idle_interval") or 60  -- 代理空闲状态检查时间间隔

agent_pool_min_size = tonumber(agent_pool_min_size)
agent_bgsave_interval = tonumber(agent_bgsave_interval)
agent_check_idle_interval = tonumber(agent_check_idle_interval)

local function close_agent(uid, subid)
	local agent = user_agent[uid]
	user_agent[uid] = nil
	if agent then
		skynet.call(gateservice, "lua", "logout", uid, subid)

		-- if agent exit, need pcall(skynet.call, agent, "lua", "logout")
		-- default: recycle(true)
		local recycle = skynet.call(agent, "lua", "logout")
		if recycle then 
			skynet.call(agent, "lua", "persistent")
			skynet.call(agent, "lua", "recycle")

			user_agent[uid] = nil 
			agent_pool[#agent_pool + 1] = agent 
			
			recycled_agents = recycled_agents + 1
		end 
	end
end

local function list_agent_pool()
	print(" ~~~ list_agent_pool ~~~ ")
	for k, v in pairs(agent_pool) do 
		print(k, v)
	end
	print(" ~~~ list_agent_pool ~~~ ")
end 

local function create_agent_pool()
	if #agent_pool < agent_pool_min_size then 
		local create_agent_count = agent_pool_min_size - #agent_pool
		logger.info("watchdog", "create_agent_pool", create_agent_count, "agents in agent_pool")

		for i = 1, create_agent_count do 
			local agent = skynet.newservice("agent", skynet.self())
			agent_pool[#agent_pool + 1] = agent
		end 
	end 
end 

local function check_idle_agents()
	logger.debug("watchdog", "check idle agent")

	for _, agent in pairs(user_agent) do 
		skynet.call(agent, "lua", "check_idle")
	end 
end 

local function bgsave_agent_state()
	logger.debug("watchdog", "let agent do persistent")

	for _, agent in pairs(user_agent) do 
		skynet.call(agent, "lua", "persistent")
	end 
end 

-- 时间：s
local check_idle_count = 0
local persistent_count = 0 
local recycled_count = 0


local function timer_watchdog()
	create_agent_pool()

	check_idle_count = check_idle_count + 1
	persistent_count = persistent_count + 1
	recycled_count = recycled_count + 1

	if check_idle_count >= agent_check_idle_interval then 
		check_idle_count = 0 
		check_idle_agents()
	end 

	if persistent_count >= agent_bgsave_interval then 
		persistent_count = 0 
		bgsave_agent_state()
	end 

	-- 每60s，检查可复用队列中的元素，完成代理的退出善后操作，重新放回到代理池
	if recycled_count >= 60 then 
		recycled_count = 0 

		if #recycle_queue > 0 then 
			for _, item in pairs(recycle_queue) do 
				local uid = item.uid 
				close_agent(uid)
			end 

			recycle_queue = {}
		end 
	end 

	skynet.timeout(100, timer_watchdog) -- 1s 
end 

function SOCKET.close(fd)
	-- print("socket close",fd)
	logger.error("watchdog", "SOCKET.close", "socket fd", fd)
	close_agent(fd)
end

function SOCKET.error(fd, msg)
	-- print("socket error",fd, msg)
	logger.error("watchdog", "SOCKET.error", "socket fd", fd)
	close_agent(fd)
end

function SOCKET.warning(fd, size)
	-- size K bytes havn't send out in fd
	-- print("socket warning", fd, size)
	logger.error("watchdog", "SOCKET.warning", "size K bytes havn't send out in fd", fd)
end

function SOCKET.data(fd, msg)
	logger.error("watchdog", "SOCKET.data", "unexpected receiving msg of fd", fd)
end

-- call by gated
function CMD.alloc_agent(uid)
	-- list_agent_pool()

	local agent 
	if user_agent[uid] then 
		logger.debug("watchdog", "user uid", uid, "is online, ignore realloc")

		agent = user_agent[uid]
	else
		logger.debug("watchdog", "alloc agent for user uid", uid)

		if #agent_pool > 0 then 
			agent = table.remove(agent_pool) -- 第二个参数：删除索引；默认尾部删除
		else 
			agent = skynet.newservice("agent", skynet.self())
		end 

		user_agent[uid] = agent 

		local init = skynet.call(agent, "lua", "load_user_data", uid) 

		-- 数据加载失败，代理回收
		if not init then 
			logger.debug("watchdog", "agent init failed and must recycle, add to free pool")

			-- agent init failed, recycle, add to agent_pool 
			agent_pool[#agent_pool + 1] = agent

			agent = nil 
			user_agent[uid] = nil 
		end 
	end 

	-- print("alloc agent : [ " .. agent .. " ] ")
	-- list_agent_pool()

	return agent 
end 

function CMD.start(conf)
	loginservice = conf.loginservice
	gateservice  = conf.gateservice
	
	return skynet.call(gateservice, "lua", "open", conf)
end

function CMD.recycle_agent(uid, agent)
	recycle_queue[#recycle_queue + 1] = {
		uid = uid,
		agent = agent,
	}
end

function CMD.afk(uid)
	logger.debug("watchdog", "uid", uid, "away from keyboard")

	skynet.call(user_agent[uid], "lua", "afk")
end 

function CMD.logout(uid)
	logger.debug("watchdog", "uid", uid, "logout")
	
	close_agent(uid)
end

-- call by gated
function CMD.auth_client_agent(agent, fd, ip) 
	skynet.call(agent, "lua", "auth_client", fd, ip)
end 

skynet.start(function()
	skynet.dispatch("lua", function(session, source, cmd, subcmd, ...)
		if cmd == "socket" then
			local f = SOCKET[subcmd]
			f(...)
			-- socket api don't need return
		else
			local f = assert(CMD[cmd])
			skynet.ret(skynet.pack(f(subcmd, ...)))
		end
	end)

	create_agent_pool()

	skynet.timeout(100, timer_watchdog)
end)
